package com.sc.gateway.core.filter;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.sc.common.entity.admin.apptoken.SysAppToken;
import com.sc.common.enums.CharsetEnum;
import com.sc.common.enums.RedisKeyEnum;
import com.sc.common.util.MyStringUtils;
import com.sc.common.util.SignUtil;
import com.sc.common.util.cache.SpringRedisTools;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
public class AccessFilterForApi {
    static Logger logger = LogManager.getLogger(AccessFilterForApi.class);

    @Autowired
    private SpringRedisTools springRedisTools;

    public Mono<Void> filter(ServerWebExchange serverWebExchange, GatewayFilterChain gatewayFilterChain){
        ServerHttpRequest request = serverWebExchange.getRequest();
        ServerHttpResponse response = serverWebExchange.getResponse();
        logger.info("网关拦截日志，请求URL={}，请求URI={},请求参数={}",request.getPath().toString(),request.getURI().toString(),request.getQueryParams());

        Map<String,String> parameterMap = new HashMap<>();
        try {
            Map<String,String> headerParameter = parseHeaderParameter(request.getHeaders());
            Map<String,String> queryParams = parseQueryParams(request.getQueryParams());

            parameterMap.putAll(headerParameter);
            parameterMap.putAll(queryParams);
        } catch (UnsupportedEncodingException e) {
            logger.error("将参数byte数组转换为Map失败",e);
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        boolean lawful = SignUtil.verifyParameter(parameterMap);
        if(lawful){
            String appId = parameterMap.get("appId");
            SysAppToken sysAppToken = getAppTokenByAppId(appId);
            if(sysAppToken == null){
                logger.warn("非法的请求：对应的appId[{}]不存在或者已过期",appId);
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }

            String signSecretKey = sysAppToken.getSignSecretKey();
            boolean verifySignFlag = SignUtil.verifySign(parameterMap,signSecretKey); // 验证签名是否合法，防止篡改请求参数
            if(!verifySignFlag){
                logger.error("非法的请求，接口签名校验不通过");
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }

            long timestamp = Long.valueOf(parameterMap.get("timestamp"));
            long signDuration = sysAppToken.getSignDuration();
            boolean verifySignTimeoutFlag = SignUtil.verifySignTimeout(timestamp,signDuration);
            if(!verifySignTimeoutFlag){
                logger.warn("非法的请求：对应的appId[{}]的签名已过期",appId);
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }

            if(hasSign(parameterMap.get("sign"))){ // 防止重复的请求，如有则拦截掉
                logger.warn("重复的请求，已经被网关拦截，签名={}",parameterMap.get("sign"));
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }else{
                response.setStatusCode(HttpStatus.OK);
                return gatewayFilterChain.filter(serverWebExchange);
            }
        }else{
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
    }


    /**
     * 解析获取参数，并封装到Map
     * @param httpHeaders
     * @return
     */
    private Map<String,String> parseHeaderParameter(HttpHeaders httpHeaders) {
        Map<String,String> parameterMap = new HashMap<>();

        if(httpHeaders.containsKey("appId")){
            parameterMap.put("appId", httpHeaders.get("appId").get(0));
        }

        if(httpHeaders.containsKey("nonce")){
            parameterMap.put("nonce", httpHeaders.get("nonce").get(0));
        }

        if(httpHeaders.containsKey("timestamp")){
            parameterMap.put("timestamp", httpHeaders.get("timestamp").get(0));
        }

        if(httpHeaders.containsKey("sign")){
            parameterMap.put("sign", httpHeaders.get("sign").get(0));
        }
        return parameterMap;
    }


    /**
     * 解析获取参数，并封装到Map
     * @param multiValueMap
     * @return
     */
    private Map<String,String> parseQueryParams(MultiValueMap<String, String> multiValueMap) throws UnsupportedEncodingException {
        Map<String,String> parameterMap = new HashMap<>();

        for(Map.Entry<String, List<String>> entry : multiValueMap.entrySet()){
            String key = entry.getKey();
            List<String> valueList = entry.getValue();
            String encoderValue = MyStringUtils.null2String(valueList.get(0));
            String value = "";
            if(MyStringUtils.isNotBlank(encoderValue)){
                value = URLDecoder.decode(encoderValue, CharsetEnum.UTF8.getValue());
            }
            parameterMap.put(key,value);
        }
        return parameterMap;
    }


    private boolean hasSign(String sign) {
        if(springRedisTools.hasKey(sign)){
            return true;
        }else {
            springRedisTools.addData(sign,sign,5, TimeUnit.MINUTES);
            return false;
        }
    }

    private SysAppToken getAppTokenByAppId(String appId) {
        String key = String.format(RedisKeyEnum.REDIS_KEY_HASH_GROUP_APP_TOKEN_BY_APP_ID.getStringValue(),appId);
        Map map = springRedisTools.getMap(key);
        if(CollectionUtil.isNotEmpty(map)){
            SysAppToken appToken = BeanUtil.mapToBean(map,SysAppToken.class,true);
            return appToken;
        }
        return null;
    }
}
